#import('dart:html');
#import('unittest/unittest.dart');
#import('unittest/html_enhanced_config.dart');
#import('../DartBubbles/Bubble.dart');
#import('../DartBubbles/BubbleDrawer.dart');
#import('../DartBubbles/BubbleGrid.dart');
#import('../DartBubbles/Colors.dart');
#import('../DartBubbles/BubbleEventCreator.dart');
#import('../DartBubbles/BubbleUserAction.dart');

/** This is the main method for running the unit tests for the Dart Bubbles app.
 *  Each group() below corresponds to a test suite for one of the dart classes.  
 *
 *  Note: The BubbleGrid object contains the list of Bubbles.
 *        Here is an example of a 2*2 grid of bubbles: 
 * 
 *        | yellow |        |
 *        | purple | purple |
 * 
 *  Some tests below represent this grid using a list of colors such as:
 *
 *  [ "yellow", null, "purple", "purple" ]
 *
 *  The list of selected bubbles is represented as a list of indices.
 *  For instance: [2,3] indicates that the purple bubbles are selected in the above 
 *  grid.
 **/

void main() {
  
  useHtmlEnhancedConfiguration();
  
  group('TestBubbleGrid', () {
    
    // Create a 7*8 grid. Until it is initialized, it has no bubbles.
    BubbleGrid game = new BubbleGrid(7, 8);

    test('TestSize', () => Expect.equals(56, game.size()));
    test('TestHeight', () => Expect.equals(7, game.height));
    test('TestLength', () => Expect.equals(8, game.length));
    test('TestInitialTotalPoints', () => Expect.equals(0, game.totalPoints));
    test('TestEmptyGrid', () => Expect.isNull(game.bubbles));
    
    // Check that initializing the grid sets the bubbles
    test('TestInitialize', () {
      game.initializeBubbles();
      Expect.equals(56, game.bubbles.length); // there should initially be 56 bubbles in the grid
    });
    
    // Create a 2*2 grid, without bubbles.
    // Use it to try some scenarios below, by setting the bubbles.
    BubbleGrid game2 = new BubbleGrid(2, 2);
    
    test('TestIsNotGameOver', () {
      setNewBubbleList(game2, [null, null, "red", "red"]);
      Expect.isFalse(game2.isGameOver());
    });
    
    // Check that it is game over if the grid is empty
    test('TestIsGameOverEmpty', () {      
      setNewBubbleList(game2, [null, null, null, null]);
      Expect.isTrue(game2.isGameOver());
    });
    
    // Check that it is game over if the grid is not empty,
    // but there are no groups of two or more bubbles of the same color.
    test('TestIsGameOverNotEmpty', () {      
      setNewBubbleList(game2, [null, null, "red", "yellow"]);
      Expect.isTrue(game2.isGameOver());
    });

    test('TestSetTotalPoints', () {
      game.totalPoints = 18;
      Expect.equals(18, game.totalPoints);
    });
    
    test('TestRemoveEmptyColumns', () {
      setNewBubbleList(game2, ["red", null, "red", null]);
      game2.removeEmptyColumns();
      List<Bubble> updatedGrid = game2.bubbles;
      Expect.listEquals([null, "red", null, "red"], colorList(updatedGrid));
    });
  
    test('TestRemoveSelectedGroup', () {
      setNewBubbleList(game2, ["red", "red", "yellow", "yellow"]);
      game2.removeGroup([2,3]);
      List<Bubble> updatedGrid = game2.bubbles;
      Expect.listEquals([null, null, "red", "red"], colorList(updatedGrid));
    });

  });

  group('TestBubbleDrawer', () {
    
    BubbleGrid game = new BubbleGrid(2, 2);
    setNewBubbleList(game, ["red", "yellow", "blue", "blue"]);
    BubbleDrawer drawer = new BubbleDrawer(game);
    drawer.drawGame();
   
    test('TestOutlineSelectedGroup', () {
      
      // A helper method used in this test for determining whether a bubble has a border on the given side
      bool hasBorder(String side, int bubbleIndex) {
        return drawer.getBubbleImage(bubbleIndex).attributes['style'].contains('border-$side-color: black');
      }
      
      // Outline the bottom row of bubbles
      drawer.outlineSelectedGroup(true, [2,3]);
      
      Expect.isTrue(hasBorder("left", 2));
      Expect.isTrue(hasBorder("top", 2));
      Expect.isTrue(hasBorder("bottom", 2));
      Expect.isFalse(hasBorder("right", 2));
      Expect.isFalse(hasBorder("left", 3));
      Expect.isTrue(hasBorder("top", 3));
      Expect.isTrue(hasBorder("bottom", 3));
      Expect.isTrue(hasBorder("right", 3));
      
      // Remove the outline from the bottom row of bubbles
      drawer.outlineSelectedGroup(false, [2,3]);
      
      Expect.isFalse(hasBorder("left", 2));
      Expect.isFalse(hasBorder("top", 2));
      Expect.isFalse(hasBorder("bottom", 2));
      Expect.isFalse(hasBorder("right", 2));
      Expect.isFalse(hasBorder("left", 3));
      Expect.isFalse(hasBorder("top", 3));
      Expect.isFalse(hasBorder("bottom", 3));
      Expect.isFalse(hasBorder("right", 3));
    });
    
    test('TestAlertGameOver', () {
      Expect.isTrue(document.query("#gameOver").attributes['style'].contains('display: none'));
      drawer.alertGameOver(100);
      Expect.isFalse(document.query("#gameOver").attributes['style'].contains('display: none'));
      Expect.equals('100', document.query("#totalPoints").innerHTML);
    });

    test('TestGetBubbleImage', () => Expect.equals(drawer.getBubbleImage(0), document.query("#game").elements.first));
    
    // Test that the message telling the user how many points they can gain by removing the selected
    // group of bubbles is properly hidden/shown.
    test('TestHideSelectedPoints', () {
      drawer.hideSelectedPoints(true);
      Expect.isTrue(document.query("#selectPoints").attributes['style'].contains('visibility: hidden'));
      drawer.hideSelectedPoints(false);
      Expect.isFalse(document.query("#selectPoints").attributes['style'].contains('visibility: hidden'));
    });
    
    test('TestSetSelectPoints', () {
      drawer.setSelectPoints(120);
      Expect.equals("120", document.query("#groupPoints").innerHTML);
    });
    
  });
  
  group('TestBubble', () {
    
    Bubble coloredBubble = new Bubble("red");
    Bubble empty = new Bubble(null);
    
    test('TestColoredBubble', () => Expect.equals("red", coloredBubble.color));
    test('TestEmpty', () => Expect.equals(null, empty.color));
    test('TestSetEmpty', () {
      coloredBubble.color = null;
      Expect.equals(null, coloredBubble.color);
    });
    
    test('TestSameColorBubblesEqual', () => Expect.isTrue((new Bubble("red")).equals(new Bubble("red"))));
    test('TestDifferentColorBubblesNotEqual', () => Expect.isFalse((new Bubble("red")).equals(new Bubble("blue"))));
  });
  
  group('TestColors', () {
    
    test('TestGetColors', () => Expect.listEquals(["red", "yellow", "green", "blue", "purple"], Colors.getColors()));
    test('TestColorSources', () {
      for (String color in Colors.getColors()) {
        Expect.equals("resources/${color}.png", Colors.getSrc(color));
      }
    });
    
  });
  
  group('TestBubbleEventCreator', () {
    
    BubbleGrid game = new BubbleGrid(2, 2);
    BubbleEventCreator eventCreator = new BubbleEventCreator(new BubbleUserAction(new BubbleDrawer(game)));
    
    test('TestNewGameEvent', () {
      // Set the new game event
      eventCreator.setNewGameButtonOnClickEvent();
      
      // Check that the bubbles and points are not initialized
      Expect.isNull(game.bubbles);
      Expect.equals(0, game.totalPoints);
      
      // Set the total points to 10, they should be reset to 0 after a new game starts.
      game.totalPoints = 10;
      Expect.equals(10, game.totalPoints);
      
      // Click the new game button
      Element newGameButton = document.query('#newGame');
      newGameButton.click();
      
      // Check that the total points have been reset, and that the list of bubbles is initialized
      Expect.equals(0, game.totalPoints);
      Expect.isNotNull(game.bubbles);
    });
    
    test('ClickBubbleEvent', () {
      // Set the grid of bubbles
      setNewBubbleList(eventCreator.action.drawer.grid, ["red", "yellow", "blue", "blue"]);
      
      // Draw the game and set onClick events for each of the bubble images
      eventCreator.action.drawer.drawGame();
      eventCreator.setBubblesOnClickEvents();
      
      // Check that no bubbles are selected
      Expect.listEquals([], eventCreator.action.selected);
      
      // Click on the third bubble (first bubble of second row)
      Element thirdBubble = eventCreator.action.drawer.getBubbleImage(2);
      thirdBubble.click();
      
      // Check that the two blue bubbles have been selected
      Expect.listEquals([2, 3], eventCreator.action.selected);
      
      // Click on the third bubble again
      thirdBubble.click();
      
      // Check that the blue bubbles have been removed
      Expect.listEquals([], eventCreator.action.selected);
      Expect.listEquals([null, null, "red", "yellow"], colorList(eventCreator.action.drawer.grid.bubbles));
      
    });
  
  });
  
  group('TestBubbleUserAction', () {
    
    BubbleGrid game = new BubbleGrid(2,2);
    setNewBubbleList(game, ["red", "yellow", "blue", "blue"]);
    BubbleUserAction action = new BubbleUserAction(new BubbleDrawer(game));
    
    test('TestNoneSelected', () => Expect.listEquals([], action.selected));
    
    test('TestSelectOneBubble', () {
      action.selected = [];
      action.selectBubble(0);
      Expect.listEquals([0], action.selected);
    });
    
    test('TestSelectSameColorBubbles', () {
      action.selected = [];
      action.selectBubble(2);
      Expect.listEquals([2,3], action.selected);
    });

    // Check that selected a bubble with no neighboors of the same color
    // does not select the bubble.
    test('TestClickBubbleSingle', () {
      action.selected = [];
      Expect.listEquals([], action.selected);
      action.clickBubble(0);
      Expect.listEquals([], action.selected);
    });
    
    // Check that after clicking a bubble with neighboors of the same color,
    // that group is selected.
    test('TestClickBubbleGroupOnce', () {
      action.selected = [];
      Expect.listEquals([], action.selected);
      action.clickBubble(2);
      Expect.listEquals([2,3], action.selected);
    });
    
    // Check that a selected group is removed after the second click.
    test('TestClickBubbleGroupTwice', () {
      // No bubbles selected
      action.selected = [];
      Expect.listEquals([], action.selected);
      
      // Click the first time
      action.clickBubble(2);
      Expect.listEquals([2,3], action.selected);
      
      // Click the second time
      action.clickBubble(2);
      
      // Bubbles should have been removeed
      Expect.listEquals([], action.selected);
      List<Bubble> bubblesWithoutGroup = [null, null, "red", "yellow"];
      Expect.equals(bubblesWithoutGroup[0], game.bubbles[0].color);
    });

  });
}

// Helper method for setting a grid of bubbles in the game.
// Takes a game instance, and a list of strings representing colors,
// where null is an empty space.
// 
// For example, the 3*3 grid of bubbles:
// 
// |  red   |  red  |        |
// | yellow |  blue | purple |
// | yellow | green | green  |
// 
// can be represented as the following list of colors:
// 
// [ "red", "red", null, "yellow", "blue", "purple", "yellow", "green", "green" ]

void setNewBubbleList(BubbleGrid game, List<String> colors) {
  Expect.equals(game.size(), colors.length, "Need the same number of colors as bubbles!");
  List<Bubble> bubbles = new List<Bubble>(colors.length);
  for (int i=0; i<colors.length; i++) {
    bubbles[i] = new Bubble(colors[i]);
  }
  game.bubbles = bubbles;
}

// Returns a list of colors corresponding to the given set of bubbles.
List<String> colorList(List<Bubble> bubbles) {
  List<String> colors = new List<String>(bubbles.length);
  for (int i=0; i<bubbles.length; i++) {
    colors[i] = bubbles[i].color;
  }
  return colors;
}
